﻿using BouncingBallGame.Common;
using BouncingBallGame.DataModel;
using BouncingBallGame.Helpers;
using BouncingBallGame.Models;
using BouncingBallGame.ViewModels.ShapeViewModels;
using Dpy.MathWinRT.Geometry.Geometry2D;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows.Input;
using Windows.Foundation;
using Windows.UI;
using Windows.UI.Popups;
using Windows.UI.Xaml.Media;

namespace BouncingBallGame.ViewModels
{
    public class EditLevelViewModel : BindableBase, IScalableViewModel
    {
        #region Fields

        private GameLevelModel gameLevel;
        private Brush background;
        private readonly PageManager pageManager = new PageManager();
        private double height;
        private double width;
        private string name;
        private double gravitiAcceleration;
        private bool? shouldDrawObstacle = true;
        private ShapeViewModelBase lastUsedShape = null;
        private bool canDeleteLastUsedShape = false;
        private double normalFrictionPercent;
        private double tangentFrictionPercent;

        private ICommand deleteShapeCommand;
        private ICommand exportCommand;
        private ICommand importCommand;
        private ICommand saveCommand;
        private ICommand deleteLevelCommand;

        #endregion


        #region Properties

        public int NextDrawableTypeIndex
        {
            get
            {
                return this.GetNextDrawableIndex(this.shouldDrawObstacle);
            }
            set
            {
                if (value != this.GetNextDrawableIndex(this.shouldDrawObstacle))
                {
                    if (value < 0)
                    {
                        this.shouldDrawObstacle = null;
                    }
                    else if (value == 0)
                    {
                        this.shouldDrawObstacle = false;
                    }
                    else
                    {
                        this.shouldDrawObstacle = true;
                    }

                    this.OnPropertyChanged("NextDrawableTypeIndex");
                }
            }
        }

        private int GetNextDrawableIndex(bool? shouldBeObstacle)
        {
            return shouldDrawObstacle == null ? -1 :
                (
                    shouldBeObstacle == true
                    ? 1
                    : 0
                );
        }

        public ICommand DeleteShape
        {
            get
            {
                if (this.deleteShapeCommand == null)
                {
                    this.deleteShapeCommand = new RelayCommand(this.HandleDeleteShapeCommand);
                }

                return this.deleteShapeCommand;
            }
        }

        public ICommand Export
        {
            get
            {
                if (this.exportCommand == null)
                {
                    this.exportCommand = new RelayCommand(this.HandleExportCommand);
                }

                return this.exportCommand;
            }
        }

        public ICommand Import
        {
            get
            {
                if (this.importCommand == null)
                {
                    this.importCommand = new RelayCommand(this.HandleImportCommand);
                }

                return this.importCommand;
            }
        }

        public ICommand Save
        {
            get
            {
                if (this.saveCommand == null)
                {
                    this.saveCommand = new RelayCommand(this.HandleSaveCommand);
                }

                return this.saveCommand;
            }
        }

        public ICommand DeleteLevel
        {
            get
            {
                if (this.deleteLevelCommand == null)
                {
                    this.deleteLevelCommand = new RelayCommand(this.HandleDeleteLevelCommand);
                }

                return this.deleteLevelCommand;
            }
        }

        public FieldViewModel Field { get; private set; }
        public BallViewModel Ball { get; private set; }

        public ObservableCollection<ShapeViewModelBase> DrawableObjects { get; set; }

        public Brush Background
        {
            get
            {
                return this.background;
            }
            set
            {
                if (this.background != value)
                {
                    this.background = value;
                    this.OnPropertyChanged("Background");
                }
            }
        }

        public double Width
        {
            get
            {
                return this.width;
            }
            set
            {
                if (this.width != value)
                {
                    this.width = value;
                    this.OnPropertyChanged("Width");
                }
            }
        }

        public double Height
        {
            get
            {
                return this.height;
            }
            set
            {
                if (this.height != value)
                {
                    this.height = value;
                    this.OnPropertyChanged("Height");
                }
            }
        }

        public PageManager PageManager
        {
            get
            {
                return this.pageManager;
            }
        }

        public GameLevelModel GameLevel
        {
            get
            {
                return this.gameLevel;
            }
            set
            {
                this.LoadGameLevel(value);
            }
        }

        public string Name
        {
            get
            {
                return this.name;
            }
            set
            {
                if (this.name != value)
                {
                    this.name = value;
                    this.OnPropertyChanged("Name");
                }
            }
        }

        public double GravityAcceleration
        {
            get
            {
                return this.gravitiAcceleration;
            }
            set
            {
                if (this.gravitiAcceleration != value)
                {
                    this.gravitiAcceleration = value;
                    this.OnPropertyChanged("GravityAcceleration");
                }
            }
        }

        public double NormalFrictionPercent
        {
            get
            {
                return this.normalFrictionPercent;
            }
            set
            {
                if (this.normalFrictionPercent != value)
                {
                    this.normalFrictionPercent = value;
                    this.OnPropertyChanged("NormalFrictionPercent");
                }
            }
        }

        public double TangentFrictionPercent
        {
            get
            {
                return this.tangentFrictionPercent;
            }
            set
            {
                if (this.tangentFrictionPercent != value)
                {
                    this.tangentFrictionPercent = value;
                    this.OnPropertyChanged("TangentFrictionPercent");
                }
            }
        }

        public ShapeViewModelBase LastUsedShape 
        {
            get
            {
                return this.lastUsedShape;
            }
            set
            {
                this.lastUsedShape = value;
                this.CanDeleteLastShape = value is ObstacleViewModel || value is GiftViewModel;
            }
        }

        public bool CanDeleteLastShape 
        {
            get
            {
                return this.canDeleteLastUsedShape;
            }
            set
            {
                if (this.canDeleteLastUsedShape != value)
                {
                    this.canDeleteLastUsedShape = value;
                    this.OnPropertyChanged("CanDeleteLastShape");
                }
            }
        }

        #endregion


        #region Constructors

        public EditLevelViewModel()
        {
            this.Background = new SolidColorBrush(Colors.Transparent);
            this.DrawableObjects = new ObservableCollection<ShapeViewModelBase>();
        }

        #endregion


        #region Methods
        
        private async void HandleDeleteLevelCommand(object parameter)
        {
            LevelsManager.RemoveLevelFromRoaming(this.Name);
            LevelsManager.RemoveSaveGameFromRoaming(this.Name);
            await new MessageDialog("Game level successfully deleted!").ShowAsync();
        }

        private async void HandleSaveCommand(object parameter)
        {
            GameLevelModel gameLevel = this.ToGameLevelModel();
            LevelsManager.AddLevelToRoaming(gameLevel);
            await new MessageDialog("Game level successfully saved!").ShowAsync();
        }

        private async void HandleImportCommand(object parameter)
        {
            GameLevelModel gameLevel = await FileManager.LoadJson();
            double width = this.Width;
            double height = this.Height;

            this.GameLevel = gameLevel;
            this.FitInSize(width, height);
        }

        private void HandleExportCommand(object parameter)
        {
            GameLevelModel gameLevel = this.ToGameLevelModel();
            FileManager.SaveJson(gameLevel, this.Name);
        }

        private GameLevelModel ToGameLevelModel()
        {
            GameLevelModel gameLevel = LevelsManager.Empty;

            gameLevel.Score = 0;
            gameLevel.Width = this.gameLevel.Width;
            gameLevel.Height = this.gameLevel.Height;
            gameLevel.Name = this.Name;

            double scaleFactor = gameLevel.Width / this.Width;

            gameLevel.GravityAcceleration = this.GravityAcceleration * scaleFactor;

            gameLevel.Ball = this.Ball.ToModel(scaleFactor);
            if (NormalFrictionPercent > 0 && NormalFrictionPercent < 100)
            {
                gameLevel.Ball.NormalCollisionSpeedLoss = NormalFrictionPercent / 100;
            }
            if (TangentFrictionPercent > 0 && TangentFrictionPercent < 100)
            {
                gameLevel.Ball.TangentCollisionSpeedLoss = TangentFrictionPercent / 100;
            }

            List<GiftModel> gifts = new List<GiftModel>();
            List<ObstacleModel> obstacles = new List<ObstacleModel>();

            foreach (var model in this.DrawableObjects)
            {
                if (model is GiftViewModel)
                {
                    gifts.Add((model as GiftViewModel).ToModel(scaleFactor));
                }
                else if (model is ObstacleViewModel)
                {
                    obstacles.Add((model as ObstacleViewModel).ToModel(scaleFactor));
                }
            }

            gameLevel.Gifts = gifts;
            gameLevel.Obstacles = obstacles;

            return gameLevel;
        }

        private void LoadGameLevel(GameLevelModel gameLevel)
        {
            this.gameLevel = gameLevel;
            this.Width = gameLevel.Width;
            this.Height = gameLevel.Height;
            this.Name = gameLevel.Name;
            this.GravityAcceleration = gameLevel.GravityAcceleration;
            this.NormalFrictionPercent = gameLevel.Ball.NormalCollisionSpeedLoss * 100;
            this.TangentFrictionPercent = gameLevel.Ball.TangentCollisionSpeedLoss * 100;

            this.DrawableObjects.Clear();

            this.Field = new FieldViewModel(0, 0, gameLevel.Height, gameLevel.Width, 0);
            this.DrawableObjects.Add(this.Field);

            this.Ball = gameLevel.Ball.ToViewModel();
            this.DrawableObjects.Add(this.Ball);

            foreach (var model in gameLevel.Obstacles)
            {
                ObstacleViewModel obstacle = model.ToViewModel();
                this.DrawableObjects.Add(obstacle);
            }

            foreach (var model in gameLevel.Gifts)
            {
                GiftViewModel gift = model.ToViewModel();
                this.DrawableObjects.Add(gift);
            }
        }

        public void Scale(double scaleFactor)
        {
            this.Width *= scaleFactor;
            this.Height *= scaleFactor;
            this.GravityAcceleration *= scaleFactor;

            foreach (var drawable in this.DrawableObjects)
            {
                drawable.Scale(scaleFactor);
            }
        }

        public void FitInSize(double maxWidth, double maxHeight)
        {
            double scaleFactor = (maxWidth / this.Width) * this.Height <= maxHeight
                ? (maxWidth / this.Width)
                : (maxHeight / this.Height);

            this.Scale(scaleFactor);
        }

        private void HandleDeleteShapeCommand(object parameter)
        {
            if (this.LastUsedShape is ObstacleViewModel || this.LastUsedShape is GiftViewModel)
            {
                this.DrawableObjects.Remove(this.LastUsedShape);
                this.LastUsedShape = null;
            }
        }

        public void DrawShape(Point startPoint, Point endPoint)
        {
            double height = Math.Abs(startPoint.Y - endPoint.Y);
            double width = Math.Abs(startPoint.X - endPoint.X);
            double x = Math.Min(startPoint.X, endPoint.X);
            double y = Math.Min(startPoint.Y, endPoint.Y);

            if (this.shouldDrawObstacle == true)
            {
                this.LastUsedShape = new ObstacleViewModel(x, y, height, width, 0);
                this.DrawableObjects.Add(this.LastUsedShape);
            }
            else if (this.shouldDrawObstacle == false)
            {
                this.LastUsedShape = new GiftViewModel(x, y, height, width, 0);
                this.DrawableObjects.Add(this.LastUsedShape);
            }
        }

        #endregion        
    }
}
